const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

// const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
// const NewSpeedMeasurePlugin = new SpeedMeasurePlugin();
// const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

// threadLoader预热：为了防止创建worker线程造成延迟过高，对worker线程进行预热
/* const threadLoader = require('thread-loader');
threadLoader.warmup(
  {
    // threadLoader options
    // 必须和loder对应的threadLoader的options保持一致，
    // 如果loader对应的threadLoader的options不同，则需要单独预热
    // workers: 2 // 进程数量 2个
  },
  [
    // 要加载的模块
    // 'babel-loader',
    'css-loader',
  ]
); */

const config = {
  // 环境标识: development / production / ...
  // 如果执行的shell脚本命令包含环境标识则优先使用shell参数
  mode: 'development',
  /**
   * 调试工具：是否 和 如何 生成 source map
  source-map：创建单独map文件，标识异常代码在源文件中的行和列
  eval-source-map：不创建map文件，标识 行 和 列
  cheap-module-source-map：创建map文件，只标识 行，不标识 列
  cheap-module-eval-source-map：不创建map文件，只标识 行，不标识 列
  */
  devtool: 'source-map',
  devServer: {
    port: 8360,
    progress: true, // 显示打包进度条
    open: true, // 自动打开浏览器
    compress: false, // 开启gzip压缩
    contentBase: path.join(__dirname, './dist'), // 静态文件目录
    // publicPath: path.join(__dirname, './dist'), // 外部资源路径
    publicPath: 'http://localhost:8360/', // 外部资源路径
    hot: true, // 启用模块热替换插件，更新时浏览器自动刷新页面
    // hotOnly: true, // 更新时浏览器不会自动
    /* proxy: { // 代理配置
      '/api/home/index': {
        // 转发当前/api路径的请求到指定域名下
        target: 'http://localhost:8001',
        pathRewrite: { '/api': '' }, // 重写请求路径
      },
    }, */
    /* before: (app) => { // mock配置
      app.get('/api/home/index', (req, res) => {
        // 请求/api时返回模拟数据
        res.json({ data: 'data' });
      });
    }, */
  },
  // 入口文件：打包起始文件，依赖图起点
  entry: './src/index',
  // 输出配置
  output: {
    // 打包文件输出目录【必须时绝对路径】
    path: path.resolve(__dirname, './dist'),
    // 打包输出文件名 name为模块的文件名，默认main
    // hash:
    // contenthash
    //
    filename: '[name]_[hash:8].js',
    // 打包依赖文件名
    chunkFilename: '[name]_[contenthash:8].js',
    // 外部资源路径
    publicPath: 'http://localhost:8360/', // 相对于模板文件 index.html
    // publicPath: path.resolve(__dirname, './dist'), // 服务器绝对路径
    // publicPath: '/dist/', // 服务器根目录绝对路径
    // publicPath: "../dist/", // 相对于模板文件 index.html
    // publicPath: 'https://cdn.example.com/assets/', // CDN（总是 HTTPS 协议）
    // publicPath: "//cdn.clound.com/dist/", // CDN（协议相同）
  },
  // 模块解析loader配置
  module: {
    /* 指定不需要解析的模块：对于第三方库或已经处理过的静态资源，
      不需要被解析，提高打包效率
      注意：此配置将排除对指定文件进行模块解析处理，
      直接使用模块源代码进行打包，减少的webpack的工作量，
      不会减少打包体积，因为指定文件不会被排除打包
      */
    noParse: /jquery/,
    rules: [
      {
        test: /\.(ts|tsx)/,
        /* include :指定需要执行的目录
          exclude ：指定不需要执行的目录
          对于node_modules或已经处理过的静态资源，不需要再次执行babel，
          通过配置exclude排除以提升效率，
          注意：此配置将排除对指定文件使用当前loader进行处理，
          减少的webpack的工作量，不会减少打包体积，
          因为指定文件不会被排除打包
        */
        exclude: /node_modules/,
        // loader:"babel-loader",
        // options: { presets: ["@babel/env"] },
        // 多个 loader 用 use 数组按执行次序进行倒序加载
        use: [
          /*
            thread-loader会在worker pool中开启一个worker进程，对其下一个loader进行多进程打包
            每一个worker都是相互独立的进程，其创建时间开销约为600ms，且进程间通信也会产生开销
            只有工作消耗时间比较长的loader才适合多进程打包，不应过渡使用
            注意：worker中运行的loader是受到限制的
              无法读取、写入、删除文件
              无法获取webpack配置选项
              无法使用自定义loader API(即 使用插件)。
          */
          // 'thread-loader',// 使用多线程loader
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true, // babel-loader缓存功能：将编译结果缓存到磁盘，再次构建时如果文件没有发生变化则会直接使用缓存
              presets: [
                '@babel/preset-react',
                '@babel/env',
              ],
              plugins: [
                ['@babel/plugin-proposal-decorators', { legacy: true }],
              ],
            },
          },
          {
            loader: 'ts-loader',
            options: {
              // 禁用类型检测（配合使用 fork plugin在另外进程中执行检测）
              transpileOnly: true,
            },
          },
        ],
      },
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: [
          // 'thread-loader',
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              presets: [
                '@babel/preset-react',
                '@babel/env',
              ],
              plugins: [
                ['@babel/plugin-proposal-decorators', { legacy: true }],
              ],
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          // process.env.NODE_ENV === "development" ?  "style-loader" : // 把css插入到head标签中
          MiniCssExtractPlugin.loader, // 把css模块分离生成样式文件
          'cache-loader', // 将编译结果缓存到磁盘，再次构建时如果文件没有发生变化则会直接使用缓存
          'thread-loader', // 由于worker进程中的loader无法操作文件，应在操作文件loader前执行
          // "css-loader?modules&localIdentName=[name]_[local]_[hash:base64:4]&importLoaders=1",
          // 字符串形式
          {// 对象形式
            loader: 'css-loader', // 把css解析创建css模块
            options: {
              camelCase: true, // 导出类名骆驼化以便在js中使用
              modules: true, // 开启css-modules
              localIdentName: '[name]_[local]_[md5:contenthash:base64:4]', // 命名规则
            },
          },
          {
            loader: 'postcss-loader', // 对实验性CSS特性进行polyfill使浏览器能够支持
            options: {
              postcssOptions: {
                plugins: [
                  'postcss-import', // 解析css内嵌@import样式文件
                  [
                    'postcss-preset-env', {// 将最新css属性转换为浏览器支持的结果
                      stage: 2, // 指定polyfill生效特性的阶段，0到4分别对应实验阶段到稳定阶段，默认2
                      browsers: 'last 2 versions', // 指定polyfill生效特性的浏览器版本，默认使用项目指定的.browserlistrc
                      autoprefixer: true, // 根据 Can I Use 中各大厂商兼容性自动添加前缀，默认开启
                    }],
                  'cssnano', // 使用cssnano压缩
                  /* 支持字符串 数组 CommonJS包 函数，详见postcss配置
                    'stylelint', // 使用stylelint格式化，建议在开发中而不是打包时进行此操作
                    require.resolve("my-postcss-plugin"),
                    myPostcssPlugin({ myOption: true }),
                  */
                ],
              },
            },
          },
          'less-loader', // 把less转换为css
        ],
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'cache-loader',
          'thread-loader',
          {
            loader: 'css-loader',
            options: {
              camelCase: true,
              modules: true,
              localIdentName: '[name]_[local]_[md5:contenthash:base64:4]',
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  'postcss-import',
                  ['postcss-preset-env', {
                    stage: 2,
                    autoprefixer: true,
                    browsers: 'last 2 versions',
                  }],
                  'cssnano',
                ],
              },
            },
          },
        ],
      },
      {
        test: /\.(png|jpe?g|gif)$/i,
        loader: 'file-loader',
        options: {
          name: '[path][name].[ext]',
        },
      },
    ],
  },
  // 插件配置
  plugins: [
    new CleanWebpackPlugin({
      // 每次打包自动清理上次输出文件
      dry: false, // Simulate the removal of files
      verbose: true, // Write Logs to Console(Always enabled when dry is true)
    }),
    new MiniCssExtractPlugin({
      // 将css分离成单独文件，不依赖于 JS
      filename: '[name]_[contenthash:8].css',
      chunkFilename: '[name]_[contenthash:8].css',
    }),
    new HtmlWebpackPlugin({
      // 输出 html文件，每一个HtmlWebpackPlugin实例对应一个页面，实现多页面打包
      template: path.resolve(__dirname, './src/index.html'), // 页面模板文件
      // templateContent:'<div id="root"></div>',// 页面模板内容 不能和模板文件同时配置
      filename: 'index.html', // 生成文件名
      hash: true, // 为入口script文件添加hash版本号
      minify: {
        removeAttributeQuotes: true, // 移除属性（非必要）双引号
        collapseInlineTagWhitespace: true, // 压缩空格和换行
      },
    }),
    // new ForkTsCheckerWebpackPlugin(), // ts类型检测插件，将在新进程中执行
    new webpack.BannerPlugin('版权声明，将被插入到所有打包生成的js文件开头'),
    /* new CopyWebpackPlugin( {
      patterns: [
        // 复制静态文件
        { from: path.resolve( __dirname, './static/fonts' ) },
      ]
    }), */
    new webpack.HotModuleReplacementPlugin(),
    /* 指定排除打包的文件，减小打包体积
      打包时将 moment 引入.locale 语言包排除
      在项目代码中使用moment时，需要手动引用中文包 import "moment/locale/zh-cn"
      否则无法设置语言为中文 moment.locale('zh-cn')
      这样相当于将于只打包了语言包中的中文部分
    */
    new webpack.IgnorePlugin(/\.\/locale/, /moment/),
    // new BundleAnalyzerPlugin() // 打包体积分析
  ],

  resolve: {
    /* 指定模块路径：默认['node_modules']
      从当前目录 ./node_modules 查找，到上级目录 ../node_modules，最后到根目录（即 npm 查找包的规则）
      直接指定项目根目录，可以减少查找耗时
      */
    modules: [path.resolve(__dirname, 'node_modules')],
    /* 指定扩展名：默认['js', 'json']
      导入模块没有后缀时，Webpack 会使用extensions后缀列表尝试匹配文件，
      如：require('./data') 时 ，Webpack 会先尝试寻找 data.js，没有再去找 data.json；
      列表越长，或者正确的后缀越往后，尝试的次数就会越多。
      频率出现高的文件后缀优先放在前面。
      列表尽可能的少，例如只有 3 个：js、jsx、json。
      书写导入语句时，尽量写上后缀名。
    */
    extensions: ['*', '.ts', '.tsx', '.js', '.jsx'],
    plugins: [// TsconfigPaths插件
      new TsconfigPathsPlugin({configFile: './tsconfig.json'}),
    ],
  },

  /**
   * 监听变化：代码变更自动打包
   * devServer将打包结果保存在内存中，不创建打包文件
   * 开启监听自动打包可以查看实时产生的打包文件
   */
  // watch: true,
  /**
   * 监听选项
   */
  /* watchOptions: {
    poll: 10, // 每秒检测变化次数
    aggregateTimeout: 1000, // 用于防抖的超时时间 ms
    ignored: /node_modules/, // 忽略监听的目录
  }, */

  // 优化
  optimization: {
    // chunk划分
    /* splitChunks: {
      cacheGroups: {
        common: {
          chunks: 'initial', // 'async'
          minSize: 1,
          minChunks: 1,
        },
        vendor: {
          priority: 1,
          test: /node_modules/,
          chunks: 'initial', // 'async'
          minSize: 1,
          minChunks: 1,
        },
      },
    }, */
    // 压缩
    /* minimize: true,
    minimizer: [
      // webpack默认代码压缩插件为uglifyjs
      new TerserPlugin({// TerserPlugin可以实现多进程并行压缩
        parallel: 4, // 开启多进程并行压缩
      }),
    ], */
  },
};

// module.exports = NewSpeedMeasurePlugin.wrap(config); // 打包速度分析打包
module.exports = config;
